﻿using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web.Script.Serialization;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Collections.Specialized;
using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using System.IO.Compression;
using System.Text.RegularExpressions;

namespace GT
{
    public static partial class Extensions
    {
        public static object CallByName(this object source, string name, object[] args)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            object ret = null;
            var methods = source.GetType().GetMethods();
            try
            {
                ret = methods.Where(i => i.Name.Equals(name) && i.GetParameters().Length == args.Length).First().Invoke(source, args);
            }
            catch { }
            return ret;
        }

        public static T CallByName<T>(this object source, string name, object[] args)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var methods = source.GetType().GetMethods();
            T ret = default(T);
            try
            {
                ret = (T)methods.Where(i => i.Name.Equals(name) && i.GetParameters().Length == args.Length).First().Invoke(source, args);
            }
            catch { }
            return (ret == null ? default(T) : (T)ret);
        }

        public static T Clone<T>(this object item)
        {
            if (item != null)
            {
                BinaryFormatter formatter = new BinaryFormatter();
                T result = default(T);
                using (MemoryStream stream = new MemoryStream())
                {

                    formatter.Serialize(stream, item);
                    stream.Seek(0, SeekOrigin.Begin);

                    result = (T)formatter.Deserialize(stream);
                }

                return result;
            }
            else
                return default(T);
        }

        public static T Coerce<T>(this object value)
        {
            T result = default(T);

            if (value != null)
            {
                TypeConverter tc = TypeDescriptor.GetConverter(typeof(T));
                if (tc.CanConvertFrom(value.GetType()))
                    result = (T)tc.ConvertFrom(value);
                else
                {
                	tc = TypeDescriptor.GetConverter(value.GetType());
                    if (tc.CanConvertTo(typeof(T)))
                        result = (T)tc.ConvertTo(value, typeof(T));
                    else
                    {
                        try
                        {
                            result = (T)value;
                            return result;
                        }
                        catch
                        {
                            // Try to copy all possible properties from the source object
                            // in to the destination object
                            List<PropertyInfo> sourceprops = new List<PropertyInfo>().Pipe(i => i.AddRange(value.GetType().GetProperties()));
                            List<PropertyInfo> destprops = new List<PropertyInfo>().Pipe(i => i.AddRange(typeof(T).GetProperties()));
                            PropertyInfo[] props = sourceprops.Intersect(destprops).ToArray();

                            props.ForEach(p =>
                                {
                                    result.SetPropertyValue(p.Name, value.GetPropertyValue(p.Name, false), false);
                                    sourceprops.Remove(p);
                                    destprops.Remove(p);
                                });

                            var sameprops = new Dictionary<PropertyInfo, PropertyInfo>();
                            destprops.Where(p => destprops.Any(q => p.Name.ToLower().Equals(q.Name.ToLower()))).ForEach(p =>
                                {
                                    sameprops[p] = sourceprops.Where(q => q.Name.Equals(p.Name)).First();
                                });

                            sameprops.Keys.ForEach(p =>
                                {
                            //        Type t = p.GetGetMethod().ReturnType;

                                    if (p.GetGetMethod().ReturnType == sameprops[p].GetGetMethod().ReturnType)
                                        result.SetPropertyValue(p.Name, value.GetPropertyValue(p.Name, false), false);
                            //        else
                            //            result.SetPropertyValue(p.Name, value.GetPropertyValue(p.Name).Coerce(t));
                                });
                        }
                    }
                }
            }
            return result;
        }

        //public static object Coerce(this object value, Type destType)
        //{
        //}

        public static T GetEnumValue<T>(this T enumeration, string memberName, bool caseSensitive = true)
            where T: struct, IConvertible
        {
            if (!typeof(T).IsEnum)
                throw new ArgumentException("Object is not an enumerated type");

            var ret = Enum.GetValues(typeof(T)).OfType<T>().Where(i =>
                {
                    if (!caseSensitive)
                        return i.ToString().ToLower().Equals(memberName.ToLower().Trim());
                    else
                        return i.ToString().Equals(memberName.Trim());
                });

            if (ret.Count() == 0)
                return default(T);

            return ret.First();
        }

        public static object GetPropertyValue(this object source, string property, bool caseSensitive = true)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (source == null)
                throw new ArgumentNullException("source");

            var sourceType = source.GetType();
            var sourceProperties = sourceType.GetProperties();

            var propertyValue = (from s in sourceProperties
                                 where (caseSensitive ? s.Name.Equals(property) : s.Name.ToLower().Equals(property.ToLower()))
                                 where (s.GetIndexParameters() == null || s.GetIndexParameters().Length == 0)
                                 select s.GetValue(source, null)).FirstOrDefault();

            return propertyValue;
        }

        public static T GetPropertyValue<T>(this object source, string property, bool caseSensitive = true)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var sourceType = source.GetType();
            var sourceProperties = sourceType.GetProperties();

            var propertyValue = (from s in sourceProperties
                                 where (caseSensitive ? s.Name.Equals(property) : s.Name.ToLower().Equals(property.ToLower()))
                                 where (s.GetIndexParameters() == null || s.GetIndexParameters().Length == 0)
                                 select s.GetValue(source, null)).FirstOrDefault();

            return propertyValue != null ? (T)propertyValue : default(T);
        }

        public static T Pipe<T>(this T obj, Action<T> action)
        {
            action(obj);
            return obj;
        }

        public static R Pipe<T, R>(this T obj, Func<T, R> func)
        {
            return func(obj);
        }

        public static T Self<T>(this T obj)
        {
            return obj;
        }

        public static object SetPropertyValue(this object source, string property, object value, bool caseSensitive = true)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var sourceType = source.GetType();
            var sourceProperties = sourceType.GetProperties();

            var propertyValue = (from s in sourceProperties
                                 where (caseSensitive ? s.Name.Equals(property) : s.Name.ToLower().Equals(property.ToLower()))
                                 where (s.GetIndexParameters() == null || s.GetIndexParameters().Length == 0)
                                 where s.CanWrite
                                 select s).FirstOrDefault();

            propertyValue.SetValue(source, value, null);
            return source;
        }

        public static byte[] ToByteArray<T>(this T obj)
        {
            BinaryFormatter serializer = new BinaryFormatter();
            byte[] ret;
            using (MemoryStream s = new MemoryStream())
            {
                serializer.Serialize(s, obj);
                ret = s.ToArray();
            }
            return ret;
        }

        // TODO: provide a better implenetation of this
        //public static byte[] ToCompressedByteArray<T>(this T obj)
        //{
        //    BinaryFormatter serializer = new BinaryFormatter();
        //    byte[] ret;
        //    using (MemoryStream s = new MemoryStream())
        //    {
        //        GZipStream compressor = new GZipStream(s, CompressionMode.Compress);
        //        serializer.Serialize(compressor, obj);
        //        ret = s.ToArray();
        //        compressor.Close();
        //    }
        //    return ret;
        //}

        public static string ToJson<T>(this T obj)
        {
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            return serializer.Serialize(obj);
        }

        public static NameValueCollection ToNameValueCollection<T>(this T obj)
        {
            PropertyInfo[] props = obj.GetType().GetProperties().Where(p => p.CanRead && !p.IsSpecialName).ToArray();
            NameValueCollection ret = new NameValueCollection();

            props.ForEach(p =>
            {
                ret[p.Name] = obj.GetPropertyValue(p.Name).Coerce<string>();
            });

            return ret;
        }

        public static string ToXml<T>(this T obj)
        {
            XDocument doc = new XDocument();
            using (XmlWriter xmlWriter = doc.CreateWriter())
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
                xmlSerializer.Serialize(xmlWriter, obj);
            }
            return doc.ToString();
        }
    }
}
